﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

using Microsoft.SharePoint;
using ListItemEditor.UI;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ListItemEditor
{
    #region helper delegates, enums, interfaces

    public delegate void UseList(SPWeb web, SPList list);
    public delegate void UseWeb(SPWeb web);

    public enum FieldDisplayType { DropDown, MultiSelect, Link }
    public enum ExploreType { Web, List, Folder, Attachments }
    public enum CheckOutStatus { None, CheckedOutCurrentUser, CheckedOutOtherUser }
    public enum CheckOutAction { CheckOut, UndoCheckOut, CheckInMinor, CheckInMajor, CheckInOverwrite }
    public enum ModerationStatus { Approved = 0, Denied = 1, Pending = 2, Draft = 3, Scheduled = 4 }
    public enum ViewScope { Default, Recursive, RecursiveAll, FilesOnly }

    public interface IGridViewDataSource
    {
        GridViewDataSourceSettings Settings { get; set; }
        DataTable GetData();
        void ApplyDataChanges(DataTable table);
        void CheckOutInFiles(List<DataRow> selectedRows, CheckOutAction checkOutAction);
        void Moderate(List<DataRow> selectedRows, ModerationStatus moderationStatus);
    }

    public interface IListDataProvider
    {
        bool IsDocumentLibrary(GridViewDataSourceSettings settings);
        List<string> GetListFields(GridViewDataSourceSettings settings);
        List<string> GetListViews(GridViewDataSourceSettings settings);
        List<SpecialFieldData> GetSpecialFieldData(GridViewDataSourceSettings settings, List<string> columnNames, bool fetchLookupData);
        CheckOutStatus GetCheckOutStatusFromDataRow(GridViewDataSourceSettings settings, DataRow row);
        ModerationStatus GetModerationStatusFromDataRow(GridViewDataSourceSettings settings, DataRow row);
        string GetListUrl(GridViewDataSourceSettings settings);
        bool Explore(GridViewDataSourceSettings settings, ExploreType exploreType, DataRow row);
        List<string> GetListNamesFromWeb(GridViewDataSourceSettings settings);
        void ModifyView(GridViewDataSourceSettings settings, ViewData oldViewData, ViewData newViewData);
        ViewData GetViewData(GridViewDataSourceSettings settings, string viewName);
    }

    #endregion

    #region data classes

    public class SpecialFieldData
    {
        public DataTable DataTable { get; set; }
        public string FieldName { get; set; }
        public string DisplayFieldName { get; set; }
        public FieldDisplayType FieldDisplayType { get; set; }
    }

    public class GridViewDataSourceSettings
    {
        public string WebUrl { get; set; }
        public string ListName { get; set; }
        public string ViewName { get; set; }
        public string Query { get; set; }
        public List<string> ViewFields { get; set; }
        public uint RowLimit { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
        public bool ResyncAfterUpdate { get; set; }
        public bool SuppressEvents { get; set; }
        public string RootFolder { get; set; }
        public ViewScope ViewScope { get; set; }

        public GridViewDataSourceSettings() { this.ResyncAfterUpdate = true; }

        public GridViewDataSourceSettings Clone()
        {
            return (GridViewDataSourceSettings)this.MemberwiseClone();
        }
    }

    #endregion

    #region Utility class

    public class Utility
    {
        public static readonly HashSet<string> UIReadonlyFields = new HashSet<string>() { "ID", "FileRef", "_UIVersionString", "_UIVersion", "FileDirRef", "Last_x0020_Modified", "Created_x0020_Date", "FSObjType", "UniqueId", "ProgId", "ScopeId", "_IsCurrentVersion", "Attachments", "CheckoutUser", "_ModerationStatus", "_Level", "owshiddenversion" };
        public static readonly HashSet<string> RequiredFields = new HashSet<string>() { "ID", "FileRef", "_ModerationStatus", "_Level", "owshiddenversion", "FSObjType" };
        public static readonly HashSet<string> LibraryRequiredFields = new HashSet<string>() { "CheckoutUser" };
        public static readonly HashSet<string> ListRequiredFields = new HashSet<string>() { "Attachments" };

        public static string GetQueryByIDs(List<string> ids)
        {
            List<string> clauses = new List<string>();
            foreach (string id in ids) clauses.Add(string.Format("<Eq><FieldRef Name=\"ID\" /><Value Type=\"Counter\">{0}</Value></Eq>", id));
            if (clauses.Count == 0) return string.Empty;

            StringBuilder sb = new StringBuilder();
            sb.Append("<Where>");
            if (clauses.Count == 1) sb.Append(clauses[0]);
            else
            {

                for (int i = 0; i < clauses.Count - 1; i++) sb.Append("<Or>");
                sb.Append(clauses[0]);
                for (int i = 1; i < clauses.Count; i++)
                {
                    sb.Append(clauses[i]);
                    sb.Append("</Or>");
                }
            }
            sb.Append("</Where>");
            return sb.ToString();
        }

        public static string GetFileNameWOExt(string name)
        {
            int pos = name.LastIndexOf(".");
            if (pos < 0) return name;
            return name.Substring(0, pos);
        }

        public static string ParseLookupValue(string value)
        {
            int pos = value.IndexOf(";#");
            if (pos < 0) return value;
            return value.Substring(pos + 2);
        }

        public static bool IsFolderFromDataRowView(DataRowView row)
        {
            return IsFolderFromDataRow(row.Row);
        }

        public static bool IsFolderFromDataRow(DataRow row)
        {
            string type = row["FSObjType"].ToString();
            if (string.IsNullOrEmpty(type)) return false;

            type = Utility.ParseLookupValue(type);
            return type.Equals("1");
        }

        public static string GetServerRelativeUrlFromDataRow(DataRow row)
        {
            string fileRef = Utility.ParseLookupValue(row["FileRef"].ToString());
            if (!fileRef.StartsWith("/")) fileRef = "/" + fileRef;
            return fileRef;
        }

        public static List<string> GetViewFieldWithRequired(List<string> viewFields, bool isLibrary)
        {
            List<string> newViewFields = viewFields == null ? new List<string>() : new List<string>(viewFields);
            HashSet<string> fields = new HashSet<string>(viewFields);
            newViewFields.AddRange(RequiredFields.Where(f => !fields.Contains(f)));
            if (isLibrary) newViewFields.AddRange(LibraryRequiredFields.Where(f => !fields.Contains(f)));
            else newViewFields.AddRange(ListRequiredFields.Where(f => !fields.Contains(f)));
            return newViewFields;
        }

        public static bool Explore(string url, ExploreType exploreType, DataRow row)
        {
            if (exploreType == ExploreType.Attachments)
            {
                if (row.RowState == DataRowState.Added) return false;
                if (!row.Table.Columns.Contains("ID") || !row.Table.Columns.Contains("Attachments")) return false;
                string id = row["ID"].ToString();
                if (string.IsNullOrEmpty(id)) return false;

                string att = row["Attachments"].ToString();
                if ("1".Equals(att) || string.Compare("true", att, true) == 0)
                {
                    url = url.TrimEnd('/') + "/Attachments/" + id;
                    Utility.OpenListInExplorer(url);
                }
                else return false;
            }
            else
            {
                Utility.OpenListInExplorer(url);
            }
            return true;
        }

        public static void OpenUseWeb(string webUrl, UseWeb useWeb)
        {
            using (SPSite site = new SPSite(webUrl))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    useWeb(web);
                }
            }
        }

        public static void OpenUseList(GridViewDataSourceSettings settings, UseList useList, bool setWebUrlAndListName)
        {
            using (SPSite site = new SPSite(settings.WebUrl))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    SPList list = null;
                    if (string.IsNullOrEmpty(settings.ListName))
                    {
                        string relUrl = new Uri(settings.WebUrl).AbsolutePath.TrimEnd('/');
                        if (relUrl.EndsWith(".aspx", StringComparison.InvariantCultureIgnoreCase)) list = web.GetListFromUrl(relUrl);
                        else list = web.GetList(relUrl);
                    }
                    else list = web.Lists[settings.ListName];

                    if (setWebUrlAndListName) { settings.WebUrl = web.Url; settings.ListName = list.Title; }
                    useList(web, list);
                }
            }
        }

        public static string EscapeForXml(string s)
        {
            return s.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("\"", "&quot;");
        }

        public static T[][] SplitInChunks<T>(T[] arr, int chunkSize)
        {
            return arr.Select((t, idx) => new { t, idx })
                .GroupBy(o => (o.idx / chunkSize), o => o.t)
                .Select(g => g.ToArray()).ToArray();
        }

        public static void OpenListInExplorer(string listUrl)
        {
            // 1st off - the web folder explorer view doesn't work at all in many cases
            // so - this upgrade is a must [Software Update for Web Folders (KB907306)]
            // http://www.microsoft.com/downloads/details.aspx?FamilyId=17C36612-632E-4C04-9382-987622ED1D64&displaylang=en
            // with it I got the web folders working on my dev win 2003 server (at least in most cases)
            // then for advanced troubleshooting you can consider this whitepaper [Whitepaper - Understanding and Troubleshooting the SharePoint Explorer View]
            // http://www.microsoft.com/Downloads/details.aspx?familyid=C523AC7A-5724-48BE-B973-641E805588F4&displaylang=en

            // about the implementation here - it opens IE with COM automation and writes a small HTML snippet in it which forces the opening of the explorer view
            // basically it's the same thing that happens when you click the standard 'open with windows explorer' 'action' menu of a document library
            // you can check this MSDN article about it [About Web Folder Behaviors]
            // http://msdn.microsoft.com/en-us/library/ms531432(VS.85).aspx
            // why not use the standard WebBrowser ActiveX - it either opens the web folder view in itself or opens the explorer view and a blank IE window with it
            // so the only way to keep it neat and tidy was COM automating IE
            object pobjMissing = System.Reflection.Missing.Value;
            using (IEComObject ie = new IEComObject())
            {
                IWebBrowser2 br = ie.Unknown as IWebBrowser2;
                br.Navigate("about:blank", ref pobjMissing, ref pobjMissing, ref pobjMissing, ref pobjMissing);
                br.Visible = false;
                object o = br.Document;

                mshtml.HTMLDocumentClass doc = o as mshtml.HTMLDocumentClass;
                string text = "<html><body><style>.httpFolder {behavior: url(#default#httpFolder);}</style><div id = 'oDAV'   class = 'httpFolder'/><script>oDAV.navigateFrame('" + listUrl.TrimEnd('/') + "','_blank');</script></body></html>";
                //doc.write(text); // strangely this doesn't work - throws excpetion - type mishmash (mismatch)
                //((mshtml.IHTMLDocument2)doc).write(text); // but this works
                doc.IHTMLDocument2_write(text); // and this also
                br.Quit();
            }
        }

        public static string GetFolderListRelativePath (string listUrl, string folderServerRelativeUrl)
        {
            string listServerRelativeUrl = new Uri(listUrl).GetComponents(UriComponents.Path, UriFormat.Unescaped);
            if (!listServerRelativeUrl.StartsWith("/")) listServerRelativeUrl = "/" + listServerRelativeUrl;

            if (folderServerRelativeUrl.StartsWith(listServerRelativeUrl, StringComparison.InvariantCultureIgnoreCase))
                return folderServerRelativeUrl.Substring(listServerRelativeUrl.Length).TrimStart('/');
            return null;
        }

        public static void MakeNewItemsFolders(DataTable table)
        {
            DataRow[] rows = table.Select(null, null, DataViewRowState.Added);
            foreach (DataRow row in rows) row["FSObjType"] = "1";
        }

        public static void AddNewItemsFolders(DataTable table, int rowNumber)
        {
            for (int i = 0; i < rowNumber; i++)
            {
                DataRow row = table.NewRow();
                row["FSObjType"] = "1";
                table.Rows.Add(row);
            }
        }

        public static List<SPFieldLookupValue> ParseMultiLookupValue(string value)
        {
            List<SPFieldLookupValue> list = new List<SPFieldLookupValue>();
            if (string.IsNullOrEmpty(value)) return list;

            string[] rawParts = value.Split(new string[] { ";#" }, StringSplitOptions.None);
            List<string> parts = new List<string>();
            StringBuilder sb = new StringBuilder();

            bool buffered = false;
            foreach (string rawPart in rawParts)
            {
                // odd number of ';' - then no real splitter
                if (((rawPart.Length - rawPart.TrimEnd(';').Length) % 2) == 1)
                {
                    buffered = true;
                    sb.Append(rawPart);
                    sb.Append(";#");
                }
                else
                {
                    string add;
                    if (buffered)
                    {
                        buffered = false;
                        sb.Append(rawPart);
                        add = sb.ToString();
                        sb.Length = 0;
                    }
                    else add = rawPart;
                    parts.Add(add.Replace(";;", ";"));
                }
            }
            for (int i = 0; i < parts.Count; i += 2)
            {
                list.Add(new SPFieldLookupValue (parts[i], i + 1 >= parts.Count ? string.Empty : parts[i + 1]));
            }
            return list;
        }

        public static string FormatMultiLookupValues(List<SPFieldLookupValue> values)
        {
            return string.Join(";#", values.Select(s => string.Format("{0};#{1}", s.LookupId, s.LookupValue.Replace(";", ";;"))).ToArray());
        }

        public static void OpenDocument(string listUrl, string documentUrl)
        {
            string url = GetFileFullUrl(listUrl, documentUrl);

            Process process = new Process();
            process.EnableRaisingEvents = false;
            process.StartInfo.UseShellExecute = true;
            process.StartInfo.FileName = url;
            process.Start();
        }

        public static string GetFileFullUrl(string listUrl, string documentServerRelativeUrl)
        {
            string url = new Uri(listUrl).GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
            url = url.TrimEnd('/') + "/" + documentServerRelativeUrl.TrimStart('/');
            return url;
        }
    }

    /// <summary>
    /// http://www.experts-exchange.com/Programming/Languages/C_Sharp/Q_23987775.html
    /// </summary>
    public static class Win32
    {
        [DllImport("user32.dll")]
        static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
    
        // Messages
        const int WM_KEYDOWN = 0x100;
        const int WM_KEYUP = 0x101;
        const int WM_CHAR = 0x105;
        const int WM_SYSKEYDOWN = 0x104;
        const int WM_SYSKEYUP = 0x105;

        public static void SendKey(IntPtr hWnd, Keys key)
        {
            SendMessage(hWnd, WM_KEYDOWN, Convert.ToInt32(key), 0);
            SendMessage(hWnd, WM_KEYUP, Convert.ToInt32(key), 0);
        }

    }

    #endregion

    #region helper classes

    public class SPFieldLookupValue
    {
        private const string delimiter = ";#";
        private int m_lookupId;
        private string m_lookupValue = string.Empty;

        public SPFieldLookupValue() {}
        public SPFieldLookupValue(string fieldValue)
        {
            if (string.IsNullOrEmpty(fieldValue)) return;
            int index = fieldValue.IndexOf(";#");
            if (index >= 0)
            {
                this.m_lookupId = int.Parse(fieldValue.Substring(0, index));
                index += 2;
                if (index < fieldValue.Length)
                {
                    this.m_lookupValue = fieldValue.Substring(index, fieldValue.Length - index);
                }
            }
            else
            {
                this.m_lookupId = int.Parse(fieldValue);
                this.m_lookupValue = string.Empty;
            }
        }

        public SPFieldLookupValue(int lookupId, string lookupValue)
        {
            this.m_lookupId = lookupId;
            this.m_lookupValue = lookupValue;
        }

        public SPFieldLookupValue(string lookupId, string lookupValue)
        {
            int.TryParse(lookupId, out this.m_lookupId);
            this.m_lookupValue = lookupValue;
        }

        public override string ToString()
        {
            return (this.m_lookupId.ToString() + delimiter + this.m_lookupValue);
        }

        public int LookupId { get { return this.m_lookupId; } set { this.m_lookupId = value; this.m_lookupValue = string.Empty; } }
        public virtual string LookupValue { get { return this.m_lookupValue; } set { this.m_lookupValue = value; } }
    }

    public class CustomIconProvider : DataGridViewTextBoxIconColumn.IIconProvider
    {
        public delegate void OnItemClicked(string itemUrl, bool isFolder);

        [ThreadStatic]
        private static Dictionary<Control, CustomIconProvider> _listIconProviderMap = new Dictionary<Control,CustomIconProvider>();
        [ThreadStatic]
        private static Dictionary<Control, CustomIconProvider> _libraryIconProviderMap = new Dictionary<Control, CustomIconProvider>();

        private bool _isLibrary;
        private OnItemClicked _itemClicked;

        private CustomIconProvider(bool isLibrary, OnItemClicked itemClicked) { this._isLibrary = isLibrary; this._itemClicked = itemClicked; }

        public static CustomIconProvider GetCustomIconProvider(Control ctrl, bool isLibrary, OnItemClicked itemClicked)
        {
            if (isLibrary)
            {
                if (!_libraryIconProviderMap.ContainsKey(ctrl)) _libraryIconProviderMap[ctrl] = new CustomIconProvider(true, itemClicked);
                return _libraryIconProviderMap[ctrl];
            }
            else
            {
                if (!_listIconProviderMap.ContainsKey(ctrl)) _listIconProviderMap[ctrl] = new CustomIconProvider(false, itemClicked);
                return _listIconProviderMap[ctrl];
            }
        }

        private DataGridViewRow GetGridRow(DataGridViewCell cell, int rowIndex)
        {
            DataGridViewRow gridRow;
            DataGridView grid = cell.DataGridView;
            if (rowIndex >= 0 && rowIndex < grid.RowCount) gridRow = grid.Rows[rowIndex];
            else gridRow = cell.OwningRow;
            return gridRow;
        }

        private bool IsFolder(DataRowView row)
        {
            return Utility.IsFolderFromDataRowView(row);
        }

        #region IIconProvider Members

        public int GetIconIndex(DataGridViewCell cell, int rowIndex)
        {
            DataGridViewRow gridRow = GetGridRow(cell, rowIndex);
            if (gridRow == null || gridRow.IsNewRow || gridRow.DataBoundItem == null) return -1;

            DataRowView row = gridRow.DataBoundItem as DataRowView;
            if (row != null)
            {
                if (this.IsFolder(row)) return 0;
                else if (this._isLibrary) return 1;
            }
            return -1;
        }

        public void OnIconMouseClick(DataGridViewCell cell, int rowIndex)
        {
            if (this._itemClicked == null) return;

            DataGridViewRow gridRow = GetGridRow(cell, rowIndex);
            if (gridRow == null || gridRow.IsNewRow || gridRow.DataBoundItem == null) return;

            DataRowView row = gridRow.DataBoundItem as DataRowView;
            if (row != null && row.Row.RowState != DataRowState.Added)
            {
                string fileRef = Utility.ParseLookupValue(row["FileRef"].ToString());
                if (!fileRef.StartsWith("/")) fileRef = "/" + fileRef;

                bool isFolder = this.IsFolder(row);
                this._itemClicked(fileRef, isFolder);
            }
        }

        #endregion
    }

    public class CustomMultiSelectTranslator : DataGridViewMultiSelectColumn.ITranslateSelectedValues
    {
        private static CustomMultiSelectTranslator _instance;
        private CustomMultiSelectTranslator() { }

        public static CustomMultiSelectTranslator Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new CustomMultiSelectTranslator();
                }
                return _instance;
            }
        }

        #region ITranslateSelectedValues Members

        public List<object> ParseSelectedValues(object value)
        {
            string s = value == null ? string.Empty : value.ToString();
            return Utility.ParseMultiLookupValue(s).Select(it => it.ToString()).Cast<object>().ToList();
        }

        public object FormatSelectedValues(List<object> selectedIDs)
        {
            return Utility.FormatMultiLookupValues (selectedIDs.Select(o => new SPFieldLookupValue(o.ToString())).ToList());
        }

        public object FormatDisplayValue(object value)
        {
            string s = value == null ? string.Empty : value.ToString();
            return string.Join (", ", Utility.ParseMultiLookupValue(s).Select(it => it.LookupValue).ToArray());
        }

        #endregion
    }

    #endregion
}